#!/usr/bin/python3
'''
这是客户端的主要页面逻辑，负责调用clouddirectory和communication，是与用户交互的主要窗口
有QManiFiles CloudClient两个类
'''
import socket
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QListWidget, QPushButton, \
								QListWidgetItem, QHBoxLayout, QVBoxLayout, QMessageBox, QLineEdit, QInputDialog, QFileDialog
from PyQt5.QtCore import QSize
import sys
import os
from clouddirectory import *
from cloudqwidget import *
from communication import *

INITIAL_WIDTH = 1200
INITIAL_HEIGHT = 800

DEBUG = 0 # 如果为1则直接登录，用于测试

# 文件操作
class QManiFiles(QWidget):
    '''
    主系统，最主要的交互逻辑
    '''
    def __init__(self, *args):
        super(QManiFiles, self).__init__()
        self.commu = args[0]
        self.user_info = args[1]
        self.myaccount = self.user_info['account']
        self.basepath = './' + self.myaccount
        
        # 所有相关文件的路径 data params share 
        self.client_data_path = self.basepath + '/data'
        self.client_etc_path = self.basepath + '/etc'
        #self.client_share_path = self.basepath +'/share'

        self.directory = FileDirectory() # 创建目录对象
        self.directory.FD_load_from_file(self.client_etc_path + '/directory.txt')
        self.current_directory = self.directory.filechain[0][0] # 设置当前目录，是一个FD_File类型的文件夹
        self.current_file_list = self.directory.FD_child_files(self.current_directory) # 当前目录对应的当前文件
        self.initUI()
        
    def initUI(self):
        self.upload_button = QPushButton('U')
        self.new_button = QPushButton('N') # 新建文件夹
        self.select_button = QPushButton('...') # 选取文件
        self.forward_button = QPushButton('F') # 前进
        self.backward_button = QPushButton('B') # 后退
        self.refresh_button = QPushButton('R') # 后退
        self.getshare_button = QPushButton('GS') # get share，获取共享文件
        self.line_edit = QLineEdit() # 显示路径
        self.line_edit.setReadOnly(True) # 设置为不可编辑
        self.line_edit2 = QLineEdit() # 上传文件路径
        self.label = QLabel('account: {}'.format(self.myaccount))
        self.line_edit3 = QLineEdit() # 获取共享文件输入框
        self.line_edit.setText(self.directory.FD_gen_path2(self.current_directory))
        
        # 到这一步就应该抛弃self.current_file_list，而由self.qfile_list_widget.file_list来维护当前文件列表
        self.qfile_list_widget = QFilelistWidget(self.current_file_list) # 注意区分file_list和qfile_list_widget，q代表是widget
        
        # 关联download函数，我要在QManiFiles实现所有的通信操作，不能把它放在QFilelistWidget中，所以接下来有点麻烦了
        self.connect_items(self.qfile_list_widget.get_items())

        # 关联函数
        self.upload_button.clicked.connect(self.upload_file)
        self.new_button.clicked.connect(self.new_folder)
        self.select_button.clicked.connect(self.select_file)
        self.forward_button.clicked.connect(self.forward)
        self.backward_button.clicked.connect(self.backward)
        self.refresh_button.clicked.connect(self.refresh)
        self.getshare_button.clicked.connect(self.getshare_file)

        self.h1_layout = QHBoxLayout()
        self.h2_layout = QHBoxLayout()
        self.h3_layout = QHBoxLayout()
        self.v1_layout = QVBoxLayout()

        self.h1_layout.addWidget(self.upload_button)
        self.h1_layout.addWidget(self.new_button)
        self.h1_layout.addWidget(self.line_edit2)
        self.h1_layout.addWidget(self.select_button)
        
        self.h2_layout.addWidget(self.forward_button)
        self.h2_layout.addWidget(self.backward_button)
        self.h2_layout.addWidget(self.refresh_button)
        self.h2_layout.addWidget(self.line_edit)

        self.h3_layout.addWidget(self.label)
        self.h3_layout.addWidget(self.line_edit3)
        self.h3_layout.addWidget(self.getshare_button)

        self.v1_layout.addLayout(self.h1_layout)
        self.v1_layout.addLayout(self.h2_layout)
        self.v1_layout.addLayout(self.h3_layout)
        self.v1_layout.addWidget(self.qfile_list_widget)

        self.setLayout(self.v1_layout)

    def connect_item(self, item):
        '''
        将fileitem中的button与对应的函数进行关联
        '''
        fileitem = self.qfile_list_widget.itemWidget(item) # 通过itemWidget获取item上嵌入的fileitem
        fileitem.share_button.clicked.connect(self.share_file)
        fileitem.download_button.clicked.connect(self.download_file)
        fileitem.delete_button.clicked.connect(self.delete_file)

    def connect_items(self, items):
        '''
        扩展connect_item
        '''
        for item in items:
            fileitem = self.qfile_list_widget.itemWidget(item) # 通过itemWidget获取item上嵌入的fileitem
            fileitem.share_button.clicked.connect(self.share_file)
            fileitem.download_button.clicked.connect(self.download_file)
            fileitem.delete_button.clicked.connect(self.delete_file) 

    def upload_file(self):
        '''
        上传一个本地文件到云端
        '''
        file_src = self.line_edit2.text()
        name = os.path.basename(file_src)
        if file_src != '' and FD_File.check_file_name(name) and os.path.isfile(file_src):
            file = FD_File('FILE', self.current_directory.level+1, name, self.current_directory.name)
            if self.directory.FD_addfile(file) > 0:
                file_dst = self.line_edit.text() + name
                flag = self.commu.upload(file_src, file_dst) # 源文件（本地路径表示）和目的文件（网络路径表示）
                if flag == 1: # 需要判断是否上传成功
                    item = self.qfile_list_widget.add_item(file)
                    self.connect_item(item) # 创建后需要关联
                else:
                    QManiFiles.error_message('上传文件错误', '文件上传过程中出错')     
            else:
                QManiFiles.error_message('新建文件夹错误', '文件名重复')
        else:
            QManiFiles.error_message('上传文件错误', '文件名不合法或文件路径错误')     

    def download_file(self):
        t = self.qfile_list_widget.which_item() # 当前目录下文件的序号
        file = self.qfile_list_widget.file_list[t] # 找到它对应的文件
        if file.type == 'FILE':
            file_src = self.directory.FD_gen_path2(file)[:-1] # 根据目录生成该文件的网盘路径，取-1是因为genpath2产生的是/1/4/，但路径不要/1/4
            file_dst = self.client_data_path
            flag = self.commu.download(file_src, file_dst)
            if flag == 1: # 需要判断是否上传成功
                QManiFiles.error_message('下载文件成功', '下载文件成功，查看 {}'.format(file_dst))
            else:   
                QManiFiles.error_message('下载文件错误', '文件下载过程中出错')
        else:
            QManiFiles.error_message('下载文件错误', '无法直接下载文件夹，请分开下载')

    def delete_file(self):
        '''
        删除文件或文件夹，如果是文件夹则需要先删除文件再删除文件夹
        删除的是file_dst
        '''
        t = self.qfile_list_widget.which_item() # 当前目录下文件的序号
        file = self.qfile_list_widget.file_list[t] # 找到它对应的文件
        if file.type == 'FILE':
            file_dst = self.line_edit.text() + file.name # 网盘路径而非本地路径
            flag = self.commu.delete(file_dst)
            if flag == 1: # 需要判断是否删除成功
                self.directory.FD_deletefile(file) # 尽管FD_deletefile可以删除目录中的所有子文件或子文件夹，把所有文件都找出来，再给服务器删掉，可以是可以，但暂时不想做 20211002
                self.qfile_list_widget.delete_item(t)
            else:
                QManiFiles.error_message('删除文件错误', '文件删除过程中出错')
                self.directory.FD_deletefile(file) # 这里大概率是找不到文件，那么就删掉吧
                self.qfile_list_widget.delete_item(t)
        else: # 如果是文件夹怎么处理
            if self.directory.FD_child_files(file) == []: # 如果下一级的子文件是空的，删除三元素（目录，图形界面-file_list，服务器）中的前两个即可
                self.directory.FD_deletefile(file) # 尽管FD_deletefile可以删除目录中的所有子文件或子文件夹，把所有文件都找出来，再给服务器删掉，可以是可以，但暂时不想做 20211002
                self.qfile_list_widget.delete_item(t)
            else:
                QManiFiles.error_message('删除文件错误', '无法删除有子文件的文件夹')

    def share_file(self):
        '''
        分享文件，这里用file_src表示是待分享的源文件，主要是为了和前面统一
        '''
        t = self.qfile_list_widget.which_item() # 当前目录下文件的序号
        file = self.qfile_list_widget.file_list[t] # 找到它对应的文件
        if file.type == 'FILE':
            account, ok = QInputDialog.getText(self, '分享文件', '请输入待分享的用户账号：')
            if ok and account != '':
                file_src = self.line_edit.text() + file.name # 网盘路径而非本地路径
                flag, info = commu.share(account, file_src) # 把账号和待分享文件的网盘路径给communication，这里必须拿到info
                if flag == 1:
                    QManiFiles.error_message('分享文件成功', '分享密码: {}/{}'.format(self.myaccount, info)) # 正确则生成相应的分享密码
                    with open(self.client_etc_path+'/'+'share.txt', 'a') as f: # 保留分享密码到params中，现在还没搞好分账号处理文件夹
                        f.write('{}/{}\n'.format(self.myaccount, info))
                else:
                    QManiFiles.error_message('分享文件错误', '{}'.format(info)) # 错误则显示错误原因，因为这次会有很多与服务器相关的错误，所以不能笼统概之
            else:
                QManiFiles.error_message('分享文件错误', '用户账号不能为空')
        else:
            QManiFiles.error_message('分享文件错误', '无法分享文件夹')

    def getshare_file(self):
        '''
        根据分享链接获得分享的文件
        '''
        temp = self.line_edit3.text()
        if QManiFiles.check_sharelink(temp) == 1:
            saccount, filename = temp.split('/')
            flag, info = commu.getshare(saccount, filename) 
            if flag == 1:
                QManiFiles.error_message('成功', '请查看./data/{}'.format(info))
            else:
                QManiFiles.error_message('错误', '{}'.format(info)) # 错误则显示错误原因，因为这次会有很多与服务器相关的错误，所以不能笼统概之
        else:
            QManiFiles.error_message('错误', '分享链接格式错误')

    @staticmethod
    def check_sharelink(name):
        '''
        符合'account/filename'格式则返回1，否则返回0
        '''
        if isinstance(name, str) and name != '' and '/' in name:
            saccount, filename = name.split('/')
            if len(filename) == 64:
                return 1
            else:
                return 0
        else:
            return 0

    def select_file(self):
        fname = QFileDialog.getOpenFileName(self, 'Open file', self.basepath)[0]
        self.line_edit2.setText(fname)

    def new_folder(self):
        '''
        新建一个文件夹，只改变目录，不必改变文件
        注意directory，current_file_list（特别是），qfile_list_widget需要改变
        '''
        name, ok = QInputDialog.getText(self, '新建文件夹', '请输入文件名：')
        if ok and FD_File.check_file_name(name) and name != '':
            file = FD_File('FOLDER', self.current_directory.level+1, name, self.current_directory.name)
            if self.directory.FD_addfile(file) > 0:
                #self.current_file_list.append(file) # current_file_list注意更下新 # 20211002应该由self.qfile_list_widget维护，不再在外面维护self.current_file_list
                item = self.qfile_list_widget.add_item(file)
                self.connect_item(item) # 关联
            else:
                QManiFiles.error_message('新建文件夹错误', '文件名重复')
        else:
            QManiFiles.error_message('新建文件夹错误', '文件名不合法')

    def forward(self):
        '''
        将当前选中的file设置为self.current_directory，根据current_directory获取新的self.current_file_list
        '''
        if self.qfile_list_widget.currentItem() == None:
            QManiFiles.error_message('前进错误', '没有选中文件夹')
        elif self.current_file_list[self.qfile_list_widget.currentRow()].type == 'FILE':
            QManiFiles.error_message('前进错误', '选中的是文件，无法前进')
        else:
            t = self.qfile_list_widget.currentRow()
            self.current_directory = self.qfile_list_widget.file_list[t]
            self.qfile_list_widget.clear_items() # 将items清空同时file_list也清空，准备更新
            temp = self.directory.FD_child_files(self.current_directory) # 获取新的file_list，即当前目录的文件夹
            self.qfile_list_widget.add_items(temp) # 根据新的当前目录文件夹，来更新self.qfile_list_widget.file_list，和items
            self.line_edit.setText(self.directory.FD_gen_path2(self.current_directory)) # 更新路径显示
            self.connect_items(self.qfile_list_widget.get_items()) # 关联操作
    
    def backward(self):
        if self.current_directory.level == 0:
            QManiFiles.error_message('后退错误', '当前目录是根目录，无法后退')
        else:
            self.current_directory = self.directory.FD_parent_file(self.current_directory)
            self.qfile_list_widget.clear_items()
            temp = self.directory.FD_child_files(self.current_directory) # 获取新的file_list，即当前目录的文件夹
            self.qfile_list_widget.add_items(temp)
            self.line_edit.setText(self.directory.FD_gen_path2(self.current_directory))
            self.connect_items(self.qfile_list_widget.get_items()) # 关联操作

    def refresh(self):
        '''
        将目录信息更新到服务器上
        '''
        flag = self.commu.updatedir(self.directory.FD_dump()) # 源文件（本地路径表示）和目的文件（网络路径表示）
        if flag != 1: # 需要判断是否上传成功
            QManiFiles.error_message('目录更新错误', '文件上传过程中出错')   

    @classmethod
    def error_message(cls, reason, hint):
        '''
        显示错误信息
        不太清除为什么用临时变量不行，必须带cls或者self，不然窗口是闪现
        '''
        cls.msgBox = QMessageBox()
        if not isinstance(reason, str): # 如果reason和hint不是字符串，需要转换成字符串 
            reason = str(reason)
        if not isinstance(hint, str):
            hint = str(hint)
        cls.msgBox.setWindowTitle(reason)
        cls.msgBox.setText(hint)
        cls.msgBox.show()


class CloudClient(QMainWindow):
    '''
    负责登录窗口和主系统的调用
    '''
    def __init__(self, *args):
        super(CloudClient, self).__init__()
        self.commu = args[0]
        # 登录系统生成
        self.login_widget = ClientLogin()
        self.login_widget.loginbutton.clicked.connect(self.login) # 将login_widget和函数关联

    def init_mainsystem(self):
        self.title = 'cloud'
        self.left = 20
        self.top = 50
        self.width = INITIAL_WIDTH
        self.height = INITIAL_HEIGHT
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        # 主系统生成
        self.main_widget = QManiFiles(self.commu, self.user_info)
        self.setCentralWidget(self.main_widget)
        self.show()

    def closeEvent(self, event):
        '''
        一旦关闭窗口则执行的操作
        '''
        self.main_widget.refresh() # 更新目录
        self.commu.close_connection()

    def login(self):
        '''
        实现两个窗口的交换，非常有意思
        登录的密码现在有三个，因为还没有实现注册
        {'dustbird':'123', 'alice':'123', 'bob':'123'}
        '''
        if DEBUG == 1:
            self.user_info = {'account':'bob','password':'123'}
            flag = self.commu.login(self.user_info)
            if flag == 1:
                self.init_mainsystem()
                self.login_widget.close()
            else:
                QManiFiles.error_message('登录错误', '用户名或密码错误')
            return 

        if self.login_widget.accountline.text() == '':
            QManiFiles.error_message('登录错误', '账号为空')
        elif self.login_widget.passwordline.text() == '':
            QManiFiles.error_message('登录错误', '密码为空')
        else:
            self.user_info = {}
            self.user_info['account'] = self.login_widget.accountline.text()
            self.user_info['password'] = self.login_widget.passwordline.text()     
            flag = self.commu.login(self.user_info)
            if flag == 1:
                self.init_mainsystem()
                self.login_widget.close()
            else:
                QManiFiles.error_message('登录错误', '用户名或密码错误')


if __name__=="__main__":
    commu = Communication(('192.168.59.133', 8080))
    app = QApplication(sys.argv)
    p = CloudClient(commu)
    sys.exit(app.exec_())


